package main

import (
	"flag"
	"fmt"
	"ibdreader/mysql_def"
	"ibdreader/page"
	"ibdreader/slice"
	"io"
	"os"
	"strconv"
	"strings"
)

var tabNote = "create table sql file path, the sql is DDL as SHOW CREATE TABLE <table_name>, \r\n" + "the file can contain multiple SQLs, the table name should match the ibd file name, \r\n" + "or else the tool is not able to identify the ibd file to read, you can generate the\r\n" + "file by executing \r\nmysqldump -d -u<username> -p<password> -h <hostname> <dbname>` in command-line."
var ibdFilePath = flag.String("ibd-file-path", "", "mandatory. innodb file path with suffix of .ibd")
var tabSqlFilePath = flag.String("tab-sql-file-path", "", tabNote)
var command = flag.String("command", "", "mandatory. command to run.\r\nvalid commands are: query-all, show-all-pages")

// var pageNum = flag.String("pagenum", "", "when command is show-page.\r\nthe pagenum is necessary")

// 运行参数示例: ./innodbReader -ibd-file-path /t.ibd -command show-all-pages

func main() {

	flag.Parse()

	if *ibdFilePath == "" {
		fmt.Println("please input ibd-file-path")
		os.Exit(0)
	}

	if *command == "" {
		fmt.Println("please input command")
		os.Exit(0)
	}

	/**
	var page int64
	if *pageNum == "show-page" {
		if *pageNum == "" {
			fmt.Println("please input pageNum")
			os.Exit(0)
		} else {
			i, err := strconv.ParseInt(*pageNum, 10, 64)
			if err != nil {
				fmt.Println("pagenum please input int type")
			}
			page = i
		}
	}
	*/

	mysql_def.PageType()

	switch *command {
	// case mysql_def.SHOW_PAGE:
	//	showPage(*ibdFilePath, page)
	case mysql_def.SHOW_ALL_PAGES:
		showAllPages(*ibdFilePath)
	case mysql_def.QUERY_ALL:
		queryAll(*ibdFilePath)
	default:
		fmt.Println("You do nothing!")
	}
}

/**
func showPage(filePath string, page int64) []byte {

	file, err1 := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if err1 != nil {
		fmt.Println("文件打开失败", err1.Error())
	}
	defer file.Close()
	fs, _ := file.Stat()
	if !checkFile(fs.Size(), filePath) {
		os.Exit(0)
	}
	_, err := file.Seek(page*mysql_def.UNIV_PAGE_SIZE, io.SeekStart)
	if err != nil {
		fmt.Println("文件读取失败", err)
	}
	b := make([]byte, mysql_def.UNIV_PAGE_SIZE)
	_, err5 := file.Read(b)
	if err5 != nil {
		fmt.Println("文件读取失败", err5)
	}
	return b
}
*/

/**
 * 页类型解析
 */
func showAllPages(filePath string) {
	file, err1 := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if err1 != nil {
		fmt.Println("文件打开失败", err1.Error())
	}
	defer file.Close()
	fs, _ := file.Stat()
	if !checkFile(fs.Size(), filePath) {
		os.Exit(0)
	}
	var j int64 = 0
	for {
		if j == fs.Size() {
			break
		}

		now, err4 := file.Seek(j, io.SeekStart)
		if err4 != nil {
			fmt.Println("文件读取失败", err4)
		}
		b := make([]byte, mysql_def.UNIV_PAGE_SIZE)
		_, err5 := file.Read(b)

		if err5 != nil {
			fmt.Println("文件读取失败", err5)
		}

		slice.IN.Position = 0
		slice.IN.Data = b
		fileHeader := page.FileHeaderReadSlice()
		var space int64
		var numPageUsed int64
		var size int64
		var xdesSize int
		if fileHeader.PageType == "FIL_PAGE_TYPE_FSP_HDR" || fileHeader.PageType == "FIL_PAGE_TYPE_XDES" {
			// 文件管理页 FSP_HDR和XDES（extent descriptor）
			fhx := page.FspHdrXesReadSlice()
			space = fhx.Fsp.Space
			numPageUsed = fhx.Fsp.NumberOfPagesUsed
			size = fhx.Fsp.Size
			xdesSize = fhx.XdesList.Len()
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType, " Space:", space, " NumPagesUsed:", numPageUsed, " Size:", size, " Xdes.size:", xdesSize)
		} else if fileHeader.PageType == "FIL_PAGE_IBUF_BITMAP" {
			// insert/change buffer
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType)
		} else if fileHeader.PageType == "FIL_PAGE_INODE" {
			// Segment 段管理页
			in := page.InodeReadSlice()
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType, " Inode.size:", in.InodeEntryList.Len())
		} else if fileHeader.PageType == "FIL_PAGE_SDI" {
			// SDI（Serialized Dictionary Information）页，MySQL8.0重新设计数据词典后引入的新产物
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType)
		} else if fileHeader.PageType == "FIL_PAGE_INDEX" {
			// 索引页，叶子节点存放行数据，非叶子节点存放目录项，在MySQL 8.0版本中，根页为page 4。
			idx := page.IndexReadSlice()
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType, " root.page:", page.IsRootPage(fileHeader), " index.id:", idx.IndexHeader.IndexId, " level:", idx.IndexHeader.PageLevel, " numOfRecs:", idx.IndexHeader.NumOfRecs, " num.dir.slot:", idx.IndexHeader.NumOfDirSlots, " garbage.space:", idx.IndexHeader.GarbageSpace)
		} else if fileHeader.PageType == "FIL_PAGE_TYPE_ALLOCATED" {
			// 预分配未使用
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type:", fileHeader.PageType)
		} else {
			// 更多类型，暂未解析
			fmt.Println("Pages Num:", j/mysql_def.UNIV_PAGE_SIZE, " Pages Type: We don't care anymore")
		}

		j = now + mysql_def.UNIV_PAGE_SIZE
	}
}

/**
 * INDEX页数据解析
 */
func queryAll(filePath string) {
	file, err1 := os.OpenFile(filePath, os.O_RDONLY, os.ModePerm)
	if err1 != nil {
		fmt.Println("文件打开失败", err1.Error())
	}
	defer file.Close()
	fs, _ := file.Stat()
	if !checkFile(fs.Size(), filePath) {
		os.Exit(0)
	}
	var j int64 = 0
	for {
		if j == fs.Size() {
			break
		}
		now, err2 := file.Seek(j, io.SeekStart)
		if err2 != nil {
			fmt.Println("文件读取失败", err2)
		}
		b := make([]byte, mysql_def.UNIV_PAGE_SIZE)
		n, err3 := file.Read(b)
		if err3 != nil {
			fmt.Println("文件读取失败", err3)
		}

		if err3 == io.EOF || n < 0 {
			break
		}
		fmt.Println(b)

		gr := page.GenericRecordReadSlice()
		fmt.Println(gr)
		j = now + mysql_def.UNIV_PAGE_SIZE
	}
}

func checkFile(fsize int64, filePath string) bool {
	pages := fsize % mysql_def.UNIV_PAGE_SIZE
	if !(pages == 0) {
		fmt.Println("文件大小异常，请检查！")
		os.Exit(0)
	}
	fmt.Println("文件名称：" + strings.Split(filePath, "/")[len(strings.Split(filePath, "/"))-1])
	fmt.Println("文件大小：" + strconv.Itoa(int(fsize)) + " byte")
	x := fsize / mysql_def.UNIV_PAGE_SIZE
	fmt.Println("文件页数：", x)
	return true
}
